MQL5相較於MQL4最大的更新便是增加了物件導向,而OOP(Object-oriented programming)可以讓我們避免做不必要的執行,使運用更加簡潔與靈活。
很多人(包括我)都曾被這個新的概念迷惑,會覺得他很抽象,但在了解基本知識後,他就變得恨和藹可親了。所以今天我就從一個新手的角度來分享物件導向是什麼,還有他在MQL5中應用的時機。
OOP的基礎建立在classes和objects上,class為創建object的範本。用實際生活舉例:假設object為一台手機,而每台手機會有唯一獨自的imei碼,而class便是製作手機的設計圖,包含內存、電池容量、鏡頭畫素等狀態與功能,廠商可以用同個class生產更多object,object變數便是用來區分不同的手機。
存取修飾詞(Access Modifiers)是用於控制類別成員(屬性和方法)可見性和訪問權限的關鍵字。它們確定了哪些部分的程式碼可以訪問類別的成員,並以何種方式訪問這些成員。以下是他的修飾詞:
public:這是最開放的存取修飾詞,表示成員可以被任何程式碼訪問。在類別的外部和內部都可以訪問。例如,一個公共方法可以在任何地方調用。
private:私有成員僅限於同一個類別內部訪問。這意味著外部程式碼無法直接訪問私有屬性或方法。私有成員通常用於實現封裝和隱藏內部實現細節。
protected:受保護成員可以在同一個類別內部訪問,以及在其子類別中訪問。這是實現繼承和多態性的重要概念。外部程式碼無法直接訪問受保護成員。
package-private(Java特有):這種存取修飾詞允許成員在同一個包(package)中訪問,但在包之外無法訪問。它通常在Java中使用。
internal(C#特有):這是C#中的一種存取修飾詞,允許成員在同一個組件(assembly)內部訪問,但在不同組件中無法訪問。它有助於實現模組化和封裝。
default(MQL5特有):如果未指定存取修飾詞,則默認為 default,表示成員可以在同一個文件中訪問,但在不同文件中無法訪問。
建構函式(Constructors)是物件導向程式設計中的特殊方法,它們用於初始化類別的新物件。創建一個新的物件時,建構函式會自動調用,以確保物件在被使用之前處於一個合適的狀態。如果沒有明確定義構造函數,則會自動創建一個預設函數來初始化變數。以下是他的特點:
名稱與類別相同:建構函式的名稱必須與其所屬的類別完全相同。
沒有返回值:建構函式通常沒有返回值,包括不返回 void。它們的主要職責是初始化物件,不需要返回任何東西。
可以有參數:建構函式可以具有參數,以便在初始化過程中接受外部提供的信息。可以根據需要對物件進行不同的初始化。
多個建構函式(方法重載):同一個類別可以擁有多個建構函式,每個建構函式可以接受不同數量和類型的參數。這被稱為方法重載(Method Overloading)。
初始化成員變數:建構函式通常用於設置物件的成員變數,以確保它們具有合適的初始值。
class MyClass {
private:
int myData;
public:
// 建構函式,無參數
MyClass() {
myData = 0; // 初始化成員變數
Print("Constructor called without parameters.");
}
// 建構函式,帶參數
MyClass(int initData) {
myData = initData; // 使用參數初始化成員變數
Print("Constructor called with parameter: ", initData);
}
// 成員函式,用於顯示成員變數的值
void DisplayData() {
Print("My Data: ", myData);
}
};
int OnInit() {
MyClass obj1; // 使用無參數建構函式
MyClass obj2(42); // 使用帶參數建構函式
obj1.DisplayData(); // 顯示 obj1 的成員變數
obj2.DisplayData(); // 顯示 obj2 的成員變數
return(INIT_SUCCEEDED);
}
//輸出:Constructor called without parameters.
//Constructor called with parameter: 42
//My Data: 0
//My Data: 42
衍生類(Derived Classes)是物件導向程式設計中的一個概念,它指的是基於一個現有的類別(稱為基類或父類)創建一個新的類別。衍生類繼承了基類的特性,包括屬性和方法,同時可以添加新的特性或修改繼承的特性。這種概念是實現繼承的基礎,建立一個階層結構的類別。以下是一些關於衍生類的重要概念:
基類(Base Class)或父類(Parent Class):基類是衍生類的原型,它包含了共享的特性和方法。基類通常是一個通用的類別,可以用來創建多個不同的衍生類。
衍生類(Derived Class)或子類(Sub Class):衍生類是基於基類創建的新類別。它可以繼承基類的成員,同時可以添加自己的成員,或者修改繼承的成員。
繼承(Inheritance):繼承是指一個衍生類可以繼承基類的屬性和方法。這使得可以在不從頭開始編寫代碼的情況下,重用和擴展現有的類別。
覆寫(Override):當衍生類需要修改基類中的方法時,可以使用方法覆寫。覆寫允許衍生類提供自己的版本,以取代基類的方法實現。
//創建parent class
class CFruit {
protected:
string name;
public:
// 建構函式
CFruit(string _name) {
name = _name;
}
// 方法
void Describe() {
Print("This is a ", name);
}
};
//創建sub class
class CApple : public CFruit { //子類:父類
public:
// 建構函式
CApple() : CFruit("Apple") {}
};
//使用
void OnStart() {
CApple apple;
apple.Describe(); // 輸出:This is a Apple
}
基類 CFruit 定義了通用的水果特性,而衍生類 CApple 繼承了這些特性並添加了自己的水果名稱。
虛函數(Virtual Functions)是物件導向程式設計中的一個重要概念,用於實現多態性(Polymorphism)。虛函數允許不同的類別在繼承層次結構中具有相同的函數名稱,但具體實現可以根據每個子類別的需要而不同。以下是虛函數的一些特點:
定義:虛函數在基類(父類)中被聲明,使用 virtual 關鍵字。
覆寫:衍生類(子類)可以覆寫基類中的虛函數,以提供自己的實現。覆寫使用 override 關鍵字(在C++中,MQL5亦同)或相應的語法(在其他語言中)來標記。
多態性(Polymorphism):虛函數實現多態性,這意味著通過基類指針或參考調用虛函數時,將執行實際對象的版本,而不僅僅是基類的版本。這使在處理不同的對象時,可以不必關心它們的實際類型。
運行時綁定:虛函數的實際實現是在運行時決定的,而不是在編譯時。這種動態決定使我們可以根據對象的實際類型調用正確的函數版本。
// 父類 - 手機
class CPhone {
protected:
string imei; // 唯一的IMEI碼
int memory; // 內存容量(GB)
int battery; // 電池容量(mAh)
public:
// 建構函式
CPhone(string _imei, int _memory, int _battery) {
imei = _imei;
memory = _memory;
battery = _battery;
}
// 虛函數 - 顯示手機資訊
virtual void ShowInfo() {
Print("IMEI: ", imei);
Print("Memory: ", memory, " GB");
Print("Battery: ", battery, " mAh");
}
};
// 衍生類 1 - Android手機
class CAndroidPhone : public CPhone {
private:
int cameraResolution; // 鏡頭畫素
public:
// 建構函式
CAndroidPhone(string _imei, int _memory, int _battery, int _cameraResolution)
: CPhone(_imei, _memory, _battery) {
cameraResolution = _cameraResolution;
}
// 覆寫虛函數 - 顯示手機資訊
void ShowInfo() override {
Print("Android Phone Info:");
CPhone::ShowInfo(); // 調用父類的虛函數
Print("Camera Resolution: ", cameraResolution, " MP");
}
};
// 衍生類 2 - iPhone手機
class CiPhone : public CPhone {
private:
string faceID; // Face ID功能
public:
// 建構函式
CiPhone(string _imei, int _memory, int _battery, string _faceID)
: CPhone(_imei, _memory, _battery) {
faceID = _faceID;
}
// 覆寫虛函數 - 顯示手機資訊
void ShowInfo() override {
Print("iPhone Info:");
CPhone::ShowInfo(); // 調用父類的虛函數
Print("Face ID: ", faceID);
}
};
// 在OnStart函式中測試手機類別
void OnStart() {
CAndroidPhone androidPhone("123456789", 64, 3000, 12);
CiPhone iPhone("987654321", 128, 2800, "TrueDepth");
CPhone* phones[] = {&androidPhone, &iPhone};
for (int i = 0; i < ArraySize(phones); i++) {
phones[i].ShowInfo(); // 使用虛函數顯示手機資訊
Print("==========");
}
}
輸出為:
Android Phone Info:
IMEI: 123456789
Memory: 64 GB
Battery: 3000 mAh
Camera Resolution: 12 MP
==========
iPhone Info:
IMEI: 987654321
Memory: 128 GB
Battery: 2800 mAh
Face ID: TrueDepth
==========
範例中我們創建了一個父類 CPhone 代表手機,它包括IMEI碼、內存、電池容量等屬性。然後創建了兩個衍生類 CAndroidPhone 和 CiPhone,分別代表Android手機和iPhone手機。每個衍生類都有自己的屬性,並覆寫了父類中的 ShowInfo 虛函數以顯示手機資訊。
最後 OnStart 函式中,我們創建了兩個手機物件並使用虛函數 ShowInfo 來顯示它們的資訊。